﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using YasharEl.Infrastructure.ApplicationModel.Bootstrap;
using YasharEl.Infrastructure.ApplicationModel.Bootstrap.Extensibility;
using YasharEl.Infrastructure.ApplicationModel.Bootstrap.Reporting;
using System.Diagnostics.Contracts;
using YasharEl.Infrastructure.ApplicationModel.Bootstrap.Execution;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.Diagnostics.Logging;

namespace YasharEl.Infrastructure.ApplicationModel.Modularity.Bootstrap
{
    public abstract class BaseModuleBootstrapper<TExtension> : IModuleBootstrapper<TExtension>
        where TExtension : IModuleComponentBootstrappingExtension
    {
        #region Fields

        private readonly BaseDependencyContainer _container;
        private readonly ILogger<BaseModuleBootstrapper<TExtension>> _logger;
        private readonly IBootstrappingExtensionHost<TExtension> _extensionHost;
        private readonly IBootstrappingProcessReporter _reporter;
        private readonly BaseModule _module;

        private IBootstrappingStrategy<TExtension> _strategy;
        private IReportingContext _reportingContext;

        #endregion

        #region Protected Fields

        /// <summary>
        /// Gets a value indicating whether this instance is disposed.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
        /// </value>
        protected bool IsDisposed { get; private set; }

        #endregion

        #region ReadOnly Properties

        protected BaseDependencyContainer Container
        {
            get { return _container; }
        }

        protected ILogger<BaseModuleBootstrapper<TExtension>> Logger
        {
            get { return _logger; }
        }

        #endregion

        #region Constructors

        public BaseModuleBootstrapper(BaseDependencyContainer container, ILogger<BaseModuleBootstrapper<TExtension>> logger, BaseModule module)
            : this(container, logger, new FifoBasedExtensionHost<TExtension>(), new NullIBootstrappingProcessReporter(), module)
        {

        }

        public BaseModuleBootstrapper(BaseDependencyContainer container, ILogger<BaseModuleBootstrapper<TExtension>> logger, IBootstrappingProcessReporter reporter, BaseModule module)
            : this(container, logger, new FifoBasedExtensionHost<TExtension>(), reporter, module)
        {
        }

        public BaseModuleBootstrapper(BaseDependencyContainer container, ILogger<BaseModuleBootstrapper<TExtension>> logger, IBootstrappingExtensionHost<TExtension> extensionHost, BaseModule module)
            : this(container, logger, extensionHost, new NullIBootstrappingProcessReporter(), module)
        {
        }

        public BaseModuleBootstrapper(BaseDependencyContainer container, ILogger<BaseModuleBootstrapper<TExtension>> logger,
            IBootstrappingExtensionHost<TExtension> extensionHost, IBootstrappingProcessReporter reporter, BaseModule module)
        {
            Contract.Requires<ArgumentNullException>(container != null, "container could not be null.");
            Contract.Requires<ArgumentNullException>(logger != null, "logger could not be null.");
            Contract.Requires<ArgumentNullException>(extensionHost != null, "extensionHost could not be null.");
            Contract.Requires<ArgumentNullException>(reporter != null, "reporter could not be null.");
            Contract.Requires<ArgumentNullException>(module != null, "module could not be null.");

            _container = container;
            _logger = logger;
            _extensionHost = extensionHost;
            _reporter = reporter;
            _module = module;
        }

        #endregion

        #region IBootstrapper<TExtension> Members

        public void Initialize(IBootstrappingStrategy<TExtension> strategy)
        {
            if (this._strategy != null)
            {
                throw new BootstrapperException("Bootstrapper can only be initialized once.");
            }

            Logger.Info("Initialize module bootstrapping process...");

            _strategy = strategy;
            _reportingContext = _strategy.CreateReportingContext();

            ConfigureContainer(_module.ModuleConfiguration);
        }

        public void Run()
        {
            if (this._strategy == null)
            {
                throw new BootstrapperException("Bootstrapper must be initialized before run or shutdown.");
            }

            ConfigureExtensions();

            RunExtensions();

            OnModuleStarted(new EventArgs());
        }

        public void Shutdown()
        {
            if (_strategy == null)
            {
                throw new BootstrapperException("Bootstrapper must be initialized before run or shutdown.");
            }

            this.Dispose();
        }

        #endregion

        #region IBootstrappingExtensionPoint<TExtension> Members

        public void AddExtension(TExtension extension)
        {
            if (this._strategy == null)
            {
                throw new BootstrapperException("Bootstrapper must be initialized before run or shutdown.");
            }

            _reportingContext.CreateExtensionContext(extension);

            extension.Container = this.Container;
            extension.Logger = this.Logger;
            extension.HostingBootstrapper = this;
            // TODO : Reference to already scanned types or scan only module dependencies
            //extension.ScannedTypes = TypesToScan;

            _extensionHost.AddExtension(extension);

            Logger.Debug("Module component '{0}' added and configured to use configuration file from : {1}", extension.Name, extension.ModuleConfiguration.FilePath);
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.Dispose(true);

            GC.SuppressFinalize(this);
        }

        ~BaseModuleBootstrapper()
        {
            this.Dispose(false);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.IsDisposed && disposing)
            {
                var syntax = _strategy.BuildShutdownSyntax();

                IExecutor<TExtension> shutdownExecutor = _strategy.CreateShutdownExecutor();
                IExecutionContext shutdownExecutionContext = _reportingContext
                    .CreateShutdownExecutionContext(shutdownExecutor, Container);

                shutdownExecutor.Execute(syntax, _extensionHost.Extensions, shutdownExecutionContext);

                _reporter.Report(_reportingContext);

                _strategy.Dispose();

                IsDisposed = true;
            }
        }

        #endregion

        #region Protected Methods

        protected virtual void ConfigureExtensions()
        {
            if (this._strategy == null)
            {
                throw new BootstrapperException("Bootstrapper must be initialized before run or shutdown.");
            }

            // Create bootstrapping extensions resolver
            var extensionResolver = _strategy.CreateExtensionResolver();

            // And add them(found extensions) to bootstrapper extension point(this)
            extensionResolver.Resolve(this);

            // Build run syntax
            var syntax = _strategy.BuildConfigureSyntax();

            // Create executor for executing Configure
            IExecutor<TExtension> configureExecutor = _strategy.CreateConfigureExecutor();

            // Create new execution context and track it using reporting context
            IExecutionContext configureExecutionContext = _reportingContext
                .CreateConfigureExecutionContext(configureExecutor, Container);

            // Execute syntax on all resolved extensions
            configureExecutor.Execute(syntax, _extensionHost.Extensions, configureExecutionContext);
        }

        protected virtual void RunExtensions()
        {
            // Build run syntax
            var syntax = _strategy.BuildRunSyntax();

            // Create executor for executing Run
            IExecutor<TExtension> runExecutor = _strategy.CreateRunExecutor();
            // Create new execution context and track it using reporting context
            IExecutionContext runExecutionContext = _reportingContext
                .CreateRunExecutionContext(runExecutor, Container);

            // Execute syntax on all resolved extensions
            runExecutor.Execute(syntax, _extensionHost.Extensions, runExecutionContext);
        }

        #endregion

        #region IModuleBootstrapper<TExtension> Events

        public event EventHandler Started;

        #endregion

        #region Started Event Fire

        protected virtual void OnModuleStarted(EventArgs e)
        {
            if (Started != null)
            {
                Started(this, e);
            }
        }

        #endregion

        #region Abstract Methods

        protected abstract void ConfigureContainer(System.Configuration.Configuration config);

        #endregion
    }
}
